﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Collections;
using Sinacor.OMM.HealthMonitor.Collector;
using Sinacor.OMM.HealthMonitor.Configuration;
using Sinacor.OMM.Util;

namespace Sinacor.OMM.HealthMonitor.Server.TcpServer
{

    public class HealthMonitorTcpServer : IDisposable
    {
        private const string TRACE_FOLDER_NAME = "Monitor Sistema";

        private const int MAX_PENDING_CONNECTIONS = 1000;
        private const int TIME_TO_RETRY_START_SERVER_TCP = 1000;
        private const int TIME_TO_CHECK_TCP_CLIENTS_QUEUE = 1;

        private bool _keepRunning;
        private int _port;

        Socket _sckServer;
        Queue<Socket> _queueSckClient;
        Dictionary<string, HealthMonitorConnectionKeeper> _openedConnectionKeepersDict;
        TraceWriter _traceWriter;

        public HealthMonitorTcpServer()
        {
            _keepRunning = true;

            _traceWriter = Tracer.GetInstance(TRACE_FOLDER_NAME, "Servidor TCP");

            _queueSckClient = new Queue<Socket>();
            _openedConnectionKeepersDict = new Dictionary<string, HealthMonitorConnectionKeeper>();

            Thread thrConnectionKeeperStarter = new Thread(new ThreadStart(ConnectionKeeperStarter));
            thrConnectionKeeperStarter.Name = "Connection Keeper Starter";
            thrConnectionKeeperStarter.Start();
        }


        public void Run()
        {
            IPEndPoint ipEndPoint;

            _traceWriter.Trace("Thread TcpServer started...");

            _keepRunning = true;
            while (_keepRunning)
            {

                try
                {
                    lock(_queueSckClient)
                        _queueSckClient.Clear();

                    _port = OMM.HealthMonitor.Configuration.HealthMonitorConfigurationManager.HealthMonitorConfiguration.TcpServerPort;
                    _traceWriter.Trace("Servidor atenderá na porta [" + _port.ToString() + "]");

                    ipEndPoint = new IPEndPoint(IPAddress.Any, _port);

                    _sckServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    _sckServer.Bind(ipEndPoint);
                    _sckServer.Listen(100);
                    _sckServer.SendTimeout = 10000;
                    _sckServer.ReceiveTimeout = 10000;

                    _traceWriter.Trace("Servidor em Listening... [" + _port.ToString() + "]");

                    while (_keepRunning)
                    {
                        _traceWriter.Trace("Servidor Aguardando nova conexão...");

                        Socket sckClient = _sckServer.Accept();

                        lock (_queueSckClient)
                            _queueSckClient.Enqueue(sckClient);

                        _traceWriter.Trace("Servidor recebeu conexão e colocou na fila para tratamento... [" + sckClient.RemoteEndPoint.ToString() + "]");
                    }

                    _traceWriter.Trace("Servidor não está aguardando conexão. _keepRunning = " + _keepRunning);

                    if (_keepRunning)
                    {
                        _traceWriter.Trace("Servidor finalizando sockets para tentar novamente. _keepRunning = " + _keepRunning);
                        CloseSocketServer();

                        _traceWriter.Trace("Servidor aguardando [" + TIME_TO_RETRY_START_SERVER_TCP + "] milisegudos.");
                        Thread.Sleep(TIME_TO_RETRY_START_SERVER_TCP);
                    }
                }
                catch (Exception ex)
                {
                    if (_keepRunning)
                    {
                        _traceWriter.Trace("ERRO no servidor socket [" + ex.Message + "]");
                        _traceWriter.Trace(ex.StackTrace);
                        _traceWriter.Trace("Servidor aguardando [" + TIME_TO_RETRY_START_SERVER_TCP + "] milisegudos.");
                        Thread.Sleep(TIME_TO_RETRY_START_SERVER_TCP);
                    }
                }
            }

            _traceWriter.Trace("Thread TcpServer stoped...");
        }

        private void ConnectionKeeperStarter()
        {
            _keepRunning = true;

            while (_keepRunning)
            {
                lock (_queueSckClient)
                {
                    while (_queueSckClient.Count > 0 && _keepRunning)
                    {
                        try
                        {
                            Socket sckClient;

                            sckClient = _queueSckClient.Dequeue();

                            HealthMonitorConnectionKeeper connectionKeeper = new HealthMonitorConnectionKeeper(sckClient);
                            connectionKeeper.OnCloseEvent += new HealthMonitorConnectionKeeper.CloseHandler(connectionKeeper_OnCloseEvent);

                            Thread thrConnectionKeeper = new Thread(new ThreadStart(connectionKeeper.Run));
                            thrConnectionKeeper.Name = "Thread ConnectionKeeper - " + sckClient.RemoteEndPoint.ToString();
                            thrConnectionKeeper.Start();

                            lock (_openedConnectionKeepersDict)
                                _openedConnectionKeepersDict.Add(sckClient.RemoteEndPoint.ToString(), connectionKeeper);
                        }
                        catch (Exception ex)
                        {
                            _traceWriter.Trace("Conexão foi derrubada pelo client antes que o servidor pudesse atender...");
                        }
                    }
                }
                Thread.Sleep(TIME_TO_CHECK_TCP_CLIENTS_QUEUE);
            }
        }

        public void SendBroadCast(byte[] buffer)
        {

            if (_openedConnectionKeepersDict != null)
            {
                lock (_openedConnectionKeepersDict)
                {
                    foreach (HealthMonitorConnectionKeeper ck in _openedConnectionKeepersDict.Values)
                    {
                        try
                        {
                            ck.Send(buffer);
                        }
                        catch (Exception ex)
                        {
                            _traceWriter.Trace("Erro SendHealthStatusUpdate...");
                            _traceWriter.Trace("SendHealthStatusUpdate: [" + ex.StackTrace);
                        }
                    }
                }
            }
        }

        private void connectionKeeper_OnCloseEvent(string remoteEndPoint)
        {
            if (_openedConnectionKeepersDict != null)
            {
                lock (_openedConnectionKeepersDict)
                {
                    _openedConnectionKeepersDict[remoteEndPoint].Dispose();
                    _openedConnectionKeepersDict.Remove(remoteEndPoint);
                }
            }
        }

        private void CloseSocketServer()
        {
            if (_sckServer != null)
            {
                try
                {
                    if (_sckServer != null)
                    {
                        //_sckServer.Shutdown(SocketShutdown.Both); // NÃO Coloque isso para fechar o server
                        //_sckServer.Disconnect(false);  // NÃO Coloque isso para fechar o server
                        _sckServer.Close();
                    }
                }
                catch (Exception ex)
                {
                    if (_traceWriter != null)
                    {
                        _traceWriter.Trace("ERRO ao fechar servidor socket [" + ex.Message + "]");
                        _traceWriter.Trace(ex.StackTrace);
                        _traceWriter.Trace("Saindo...");
                    }
                }
                finally
                {
                    _sckServer = null;
                }
            }
        }

        public void Dispose()
        {
            _keepRunning = false;

            try
            {
                CloseSocketServer();

                if (_traceWriter != null)
                    _traceWriter.Trace("Saindo....");
            }
            catch (Exception ex)
            {
                if (_traceWriter != null)
                {
                    _traceWriter.Trace("ERRO ao fechar servidor socket [" + ex.Message + "]");
                    _traceWriter.Trace(ex.StackTrace);
                    _traceWriter.Trace("Saindo...");
                }
            }

            if (_queueSckClient != null)
            lock (_queueSckClient)
            {
                while (_queueSckClient != null && _queueSckClient.Count > 0)
                    _queueSckClient.Dequeue().Close();

                if (_queueSckClient != null)
                    _queueSckClient.Clear();
            }

            if (_openedConnectionKeepersDict != null)
            {
                lock (_openedConnectionKeepersDict)
                {
                    foreach (HealthMonitorConnectionKeeper h in _openedConnectionKeepersDict.Values)
                    {
                        try
                        {
                            if(h != null)
                                h.Dispose();
                        }
                        catch (Exception ex)
                        {
                            if (_traceWriter != null)
                            {
                                _traceWriter.Trace("ERRO fechar connection keeper");
                                _traceWriter.Trace(ex.StackTrace);
                                _traceWriter.Trace("Saindo...");
                            }
                        }
                    }
                    if(_openedConnectionKeepersDict != null)
                        _openedConnectionKeepersDict.Clear();
                }
            }
            _queueSckClient = null;
            _openedConnectionKeepersDict = null;

        }
    }
}
